package com.symaster.common.thread;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Closeable;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 守护线程
 * <p>
 * 通过构造方法来创建一条线程, 线程名称必须唯一
 * 通过start()方法来启动这条线程
 * 通过stop()方法来停止线程
 * 注意:如果线程内有使用io,请在stopEvent()事件内释放掉
 *
 * @author yinmiao
 * @date 2021-07-12 20:10
 */
public abstract class WatchThread implements Runnable, Closeable {

    private static final Logger logger = LoggerFactory.getLogger(WatchThread.class);

    private final Object initObj = new Object();
    private final Object startObj = new Object();
    private final Object stopObj = new Object();

    /**
     * 当前线程
     */
    private final Thread thread;
    /**
     * 线程名称
     */
    private final String name;
    /**
     * 创建时间
     */
    private final Date createTime;
    /**
     * 休眠间隔时间; 不能为负数
     */
    private int fixedDelay;
    /**
     * false则代表当前线程已释放
     */
    private boolean alive = true;
    /**
     * 是否继续运行
     */
    private boolean run = false;
    /**
     * 是否正在运行
     */
    private boolean runnable = false;
    /**
     * 启动时间(调用start()时时间)
     */
    private Date startTime;

    public WatchThread(int fixedDelay) throws IllegalArgumentException {
        this(fixedDelay, WatchThreadNameAllocator.getName());
    }

    public WatchThread(int fixedDelay, String name) throws IllegalArgumentException {
        this.createTime = new Date();
        this.fixedDelay = Math.max(fixedDelay, 0);
        this.name = name;
        this.thread = new Thread(this);
        this.thread.setName(name);

        synchronized (this.initObj) {
            try {
                this.thread.start();
                this.initObj.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

    @Override
    public void run() {

        boolean init = true;
        boolean error = false;
        boolean stopEventExecute = false;
        try {
            while (alive) {
                if (init && !run) {
                    synchronized (this.startObj) {
                        synchronized (this.initObj) {
                            this.initObj.notifyAll();
                        }
                        try {
                            this.startObj.wait();
                        } catch (InterruptedException e) {
                            break;
                        }
                        if (!alive) {
                            break;
                        }
                        init = false;
                        startEvent();
                    }
                }

                try {
                    runnable = true;
                    error = false;
                    stopEventExecute = true;
                    execute();
                    mySleep();
                } catch (Throwable e) {
                    error = true;
                    if (!errorHandler(e)) {
                        return;
                    }
                    mySleep();
                }

                if (!run) {
                    runnable = false;
                    stopListener(0);
                    stopEventExecute = false;
                    waitStart();
                    if (alive) {
                        startEvent();
                    }
                }
            }
        } finally {
            if (stopEventExecute) {
                stopListener(error ? -1 : 0);
            }
            alive = false;
            runnable = false;
        }
    }

    private void mySleep() {
        if (fixedDelay > 0) {
            try {
                TimeUnit.MILLISECONDS.sleep(fixedDelay);
            } catch (InterruptedException ignored) {
            }
        }
    }

    private void stopListener(int flag) {
        try {
            stopEvent(flag);
        } catch (Throwable ignored) {
        } finally {
            synchronized (stopObj) {
                stopObj.notifyAll();
            }
        }
    }

    private void waitStart() {
        try {
            synchronized (startObj) {
                startObj.wait();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    private void notifyStart() {
        synchronized (startObj) {
            startObj.notifyAll();
        }
    }

    /**
     * 开始事件
     */
    protected void startEvent() {
        logger.debug("{} 启动线程", getThread().getName());
    }

    /**
     * 执行任务
     */
    protected abstract void execute() throws Throwable;

    /**
     * 结束事件
     *
     * @param o 1 为强制结束;  0 为等待结束; -1为异常结束
     */
    protected void stopEvent(int o) {
        logger.debug("{} {} o={}", getThread().getName(), o == -1 ? "线程异常停止" : "线程停止", o);
    }

    protected boolean init() {
        return true;
    }

    /**
     * 错误处理
     *
     * @return 返回false将结束线程执行 (默认为true)
     */
    protected boolean errorHandler(Throwable e) {
        e.printStackTrace();
        return true;
    }

    /**
     * 线程释放前事件
     */
    protected void closeEvent() {

    }

    /**
     * 启动线程
     */
    public void start() {
        synchronized (this.startObj) {
            if (!alive) {
                throw new IllegalArgumentException("线程已死亡");
            }
            if (run && init()) {
                return;
            }
            this.startTime = new Date();
            this.run = true;
            this.startObj.notifyAll();
        }
    }

    /**
     * 强制停止线程
     */
    public void stop() {
        if (!alive) {
            return;
        }
        if (!run) {
            return;
        }
        run = false;
    }

    /**
     * 等待线程停止
     */
    public void watchStop() {
        if (!alive) {
            return;
        }
        if (!run) {
            return;
        }
        try {
            synchronized (this.stopObj) {
                stop();
                this.stopObj.wait();
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("thread interrupted the current thread before or while the current thread was waiting for a notification.");
        }
    }

    /**
     * 等待停止线程
     */
    public void watchStop(long timeout, TimeUnit unit) {
        if (!alive) {
            return;
        }
        if (!run) {
            return;
        }
        try {
            synchronized (this.stopObj) {
                stop();
                long convert = TimeUnit.MILLISECONDS.convert(timeout, unit);
                this.stopObj.wait(convert);
            }
        } catch (InterruptedException e) {
            throw new RuntimeException("thread interrupted the current thread before or while the current thread was waiting for a notification.");
        }
    }

    public Integer getFixedDelay() {
        return fixedDelay;
    }

    public void setFixedDelay(Integer fixedDelay) {
        this.fixedDelay = fixedDelay;
    }

    public String getName() {
        return name;
    }

    public Thread getThread() {
        return this.thread;
    }

    public boolean runnable() {
        return runnable;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public Date getStartTime() {
        return startTime;
    }

    @Override
    public void close() throws IOException {
        if (!alive) {
            return;
        }
        alive = false;
        if (!run) {
            notifyStart();
        }
    }
}
